/******************************************************
Cursor read

(c) 1997 Innobase Oy

Created 2/16/1997 Heikki Tuuri
*******************************************************/

#include "read0read.h"

#ifdef UNIV_NONINL
#include "read0read.ic"
#endif

#include "srv0srv.h"
#include "trx0sys.h"

/*************************************************************************
Creates a read view object. */
UNIV_INLINE
read_view_t *read_view_create_low(
    /*=================*/
    /* out, own: read view struct */
    ulint n,          /* in: number of cells in the trx_ids array */
    mem_heap_t *heap) /* in: memory heap from which allocated */
{
  read_view_t *view;

  view = mem_heap_alloc(heap, sizeof(read_view_t));

  view->n_trx_ids = n;
  view->trx_ids = mem_heap_alloc(heap, n * sizeof(dulint));

  return (view);
}

/*************************************************************************
Makes a copy of the oldest existing read view, with the exception that also
the creating trx of the oldest view is set as not visible in the 'copied'
view. Opens a new view if no views currently exist. The view must be closed
with ..._close. This is used in purge. */

read_view_t *read_view_oldest_copy_or_open_new(
    /*==============================*/
    /* out, own: read view struct */
    trx_t *cr_trx,    /* in: creating transaction, or NULL */
    mem_heap_t *heap) /* in: memory heap from which allocated */
{
  read_view_t *old_view;
  read_view_t *view_copy;
  ibool needs_insert = TRUE;
  ulint insert_done = 0;
  ulint n;
  ulint i;

#ifdef UNIV_SYNC_DEBUG
  ut_ad(mutex_own(&kernel_mutex));
#endif /* UNIV_SYNC_DEBUG */
  old_view = UT_LIST_GET_LAST(trx_sys->view_list);

  if (old_view == NULL)
  {
    return (read_view_open_now(cr_trx, heap));
  }

  n = old_view->n_trx_ids;

  if (old_view->creator)
  {
    n++;
  }
  else
  {
    needs_insert = FALSE;
  }

  view_copy = read_view_create_low(n, heap);

  /* Insert the id of the creator in the right place of the descending
  array of ids, if needs_insert is TRUE: */

  i = 0;
  while (i < n)
  {
    if (needs_insert &&
        (i >= old_view->n_trx_ids || ut_dulint_cmp(old_view->creator->id, read_view_get_nth_trx_id(old_view, i)) > 0))
    {
      read_view_set_nth_trx_id(view_copy, i, old_view->creator->id);
      needs_insert = FALSE;
      insert_done = 1;
    }
    else
    {
      read_view_set_nth_trx_id(view_copy, i, read_view_get_nth_trx_id(old_view, i - insert_done));
    }

    i++;
  }

  view_copy->creator = cr_trx;

  view_copy->low_limit_no = old_view->low_limit_no;
  view_copy->low_limit_id = old_view->low_limit_id;

  view_copy->can_be_too_old = FALSE;

  if (n > 0)
  {
    /* The last active transaction has the smallest id: */
    view_copy->up_limit_id = read_view_get_nth_trx_id(view_copy, n - 1);
  }
  else
  {
    view_copy->up_limit_id = old_view->up_limit_id;
  }

  UT_LIST_ADD_LAST(view_list, trx_sys->view_list, view_copy);

  return (view_copy);
}

/*************************************************************************
Opens a read view where exactly the transactions serialized before this
point in time are seen in the view. */

read_view_t *read_view_open_now(
    /*===============*/
    /* out, own: read view struct */
    trx_t *cr_trx,    /* in: creating transaction, or NULL */
    mem_heap_t *heap) /* in: memory heap from which allocated */
{
  read_view_t *view;
  trx_t *trx;
  ulint n;
#ifdef UNIV_SYNC_DEBUG
  ut_ad(mutex_own(&kernel_mutex));
#endif /* UNIV_SYNC_DEBUG */
  view = read_view_create_low(UT_LIST_GET_LEN(trx_sys->trx_list), heap);

  view->creator = cr_trx;

  /* No future transactions should be visible in the view */

  view->low_limit_no = trx_sys->max_trx_id;
  view->low_limit_id = view->low_limit_no;

  view->can_be_too_old = FALSE;

  n = 0;
  trx = UT_LIST_GET_FIRST(trx_sys->trx_list);

  /* No active transaction should be visible, except cr_trx */

  while (trx)
  {
    if (trx != cr_trx && (trx->conc_state == TRX_ACTIVE || trx->conc_state == TRX_PREPARED))
    {
      read_view_set_nth_trx_id(view, n, trx->id);

      n++;

      /* NOTE that a transaction whose trx number is <
      trx_sys->max_trx_id can still be active, if it is
      in the middle of its commit! Note that when a
      transaction starts, we initialize trx->no to
      ut_dulint_max. */

      if (ut_dulint_cmp(view->low_limit_no, trx->no) > 0)
      {
        view->low_limit_no = trx->no;
      }
    }

    trx = UT_LIST_GET_NEXT(trx_list, trx);
  }

  view->n_trx_ids = n;

  if (n > 0)
  {
    /* The last active transaction has the smallest id: */
    view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);
  }
  else
  {
    view->up_limit_id = view->low_limit_id;
  }

  UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);

  return (view);
}

/*************************************************************************
Closes a read view. */

void read_view_close(
    /*============*/
    read_view_t *view) /* in: read view */
{
#ifdef UNIV_SYNC_DEBUG
  ut_ad(mutex_own(&kernel_mutex));
#endif /* UNIV_SYNC_DEBUG */
  UT_LIST_REMOVE(view_list, trx_sys->view_list, view);
}

/*************************************************************************
Closes a consistent read view for MySQL. This function is called at an SQL
statement end if the trx isolation level is <= TRX_ISO_READ_COMMITTED. */

void read_view_close_for_mysql(
    /*======================*/
    trx_t *trx) /* in: trx which has a read view */
{
  ut_a(trx->global_read_view);

  mutex_enter(&kernel_mutex);

  read_view_close(trx->global_read_view);

  mem_heap_empty(trx->global_read_view_heap);

  trx->read_view = NULL;
  trx->global_read_view = NULL;

  mutex_exit(&kernel_mutex);
}

/*************************************************************************
Prints a read view to stderr. */

void read_view_print(
    /*============*/
    read_view_t *view) /* in: read view */
{
  ulint n_ids;
  ulint i;

  fprintf(stderr, "Read view low limit trx n:o %lu %lu\n", (ulong)ut_dulint_get_high(view->low_limit_no),
          (ulong)ut_dulint_get_low(view->low_limit_no));

  fprintf(stderr, "Read view up limit trx id %lu %lu\n", (ulong)ut_dulint_get_high(view->up_limit_id),
          (ulong)ut_dulint_get_low(view->up_limit_id));

  fprintf(stderr, "Read view low limit trx id %lu %lu\n", (ulong)ut_dulint_get_high(view->low_limit_id),
          (ulong)ut_dulint_get_low(view->low_limit_id));

  fprintf(stderr, "Read view individually stored trx ids:\n");

  n_ids = view->n_trx_ids;

  for (i = 0; i < n_ids; i++)
  {
    fprintf(stderr, "Read view trx id %lu %lu\n", (ulong)ut_dulint_get_high(read_view_get_nth_trx_id(view, i)),
            (ulong)ut_dulint_get_low(read_view_get_nth_trx_id(view, i)));
  }
}

/*************************************************************************
Create a consistent cursor view for mysql to be used in cursors. In this
consistent read view modifications done by the creating transaction or future
transactions are not visible. */

cursor_view_t *read_cursor_view_create_for_mysql(
    /*==============================*/
    trx_t *cr_trx) /* in: trx where cursor view is created */
{
  cursor_view_t *curview;
  read_view_t *view;
  mem_heap_t *heap;
  trx_t *trx;
  ulint n;

  ut_a(cr_trx);

  /* Use larger heap than in trx_create when creating a read_view
  because cursors are quite long. */

  heap = mem_heap_create(512);

  curview = (cursor_view_t *)mem_heap_alloc(heap, sizeof(cursor_view_t));
  curview->heap = heap;

  /* Drop cursor tables from consideration when evaluating the need of
    auto-commit */
  curview->n_mysql_tables_in_use = cr_trx->n_mysql_tables_in_use;
  cr_trx->n_mysql_tables_in_use = 0;

  mutex_enter(&kernel_mutex);

  curview->read_view = read_view_create_low(UT_LIST_GET_LEN(trx_sys->trx_list), curview->heap);

  view = curview->read_view;
  view->creator = cr_trx;

  /* No future transactions should be visible in the view */

  view->low_limit_no = trx_sys->max_trx_id;
  view->low_limit_id = view->low_limit_no;

  view->can_be_too_old = FALSE;

  n = 0;
  trx = UT_LIST_GET_FIRST(trx_sys->trx_list);

  /* No active transaction should be visible, except cr_trx.
  This is quick fix for a bug 12456 and needs to be fixed when
  semi-consistent high-granularity read view is implemented. */

  while (trx)
  {
    if (trx != cr_trx && (trx->conc_state == TRX_ACTIVE || trx->conc_state == TRX_PREPARED))
    {
      read_view_set_nth_trx_id(view, n, trx->id);

      n++;

      /* NOTE that a transaction whose trx number is <
      trx_sys->max_trx_id can still be active, if it is
      in the middle of its commit! Note that when a
      transaction starts, we initialize trx->no to
      ut_dulint_max. */

      if (ut_dulint_cmp(view->low_limit_no, trx->no) > 0)
      {
        view->low_limit_no = trx->no;
      }
    }

    trx = UT_LIST_GET_NEXT(trx_list, trx);
  }

  view->n_trx_ids = n;

  if (n > 0)
  {
    /* The last active transaction has the smallest id: */
    view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);
  }
  else
  {
    view->up_limit_id = view->low_limit_id;
  }

  UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);

  mutex_exit(&kernel_mutex);

  return (curview);
}

/*************************************************************************
Close a given consistent cursor view for mysql and restore global read view
back to a transaction read view. */

void read_cursor_view_close_for_mysql(
    /*=============================*/
    trx_t *trx,             /* in: trx */
    cursor_view_t *curview) /* in: cursor view to be closed */
{
  ut_a(curview);
  ut_a(curview->read_view);
  ut_a(curview->heap);

  /* Add cursor's tables to the global count of active tables that
    belong to this transaction */
  trx->n_mysql_tables_in_use += curview->n_mysql_tables_in_use;

  mutex_enter(&kernel_mutex);

  read_view_close(curview->read_view);
  trx->read_view = trx->global_read_view;

  mutex_exit(&kernel_mutex);

  mem_heap_free(curview->heap);
}

/*************************************************************************
This function sets a given consistent cursor view to a transaction
read view if given consistent cursor view is not NULL. Otherwise, function
restores a global read view to a transaction read view. */

void read_cursor_set_for_mysql(
    /*======================*/
    trx_t *trx,             /* in: transaction where cursor is set */
    cursor_view_t *curview) /* in: consistent cursor view to be set */
{
  ut_a(trx);

  mutex_enter(&kernel_mutex);

  if (UNIV_LIKELY(curview != NULL))
  {
    trx->read_view = curview->read_view;
  }
  else
  {
    trx->read_view = trx->global_read_view;
  }

  mutex_exit(&kernel_mutex);
}
